问题
compareTo方法是Comparable接口中唯一的方法,不但允许进行简单的等同性比较,而且允许执行顺序比较。一旦实现了Comparable接口,就可以跟许多泛型方法以及依赖于该接口的集合实现类进行协作。实现CompareTo方法有哪些规范?
解决
使用compareTo方法有一个重要的约定,就是通常情况下compareTo方法施加的等同性测试和equals方法一致。如果不一致的话,集合接口一般是使用equals方法来进行等同性测试,而有序集合是采用compareTo方法进行等同性测试,如果两者不一致的话,容易造成灾难性的后果;
将对象与指定的对象进行比较。当该对象小于、等于或者大于指定对象的时候,分别返回一个负整数,零或者正整数,如果由于指定对象的类型而无法与该对象进行比较,则抛出ClassCastException。在下面的说明中,符号sgn(表达式)表示数学中的signum函数,它根据表达式(expression)的值为负值、零和正值,分别返回-1、0、1。
- 必须确保所有的x和y都满足sgn(x.compareTo(y)) == -sgn(y.compareTo(x))。这也暗示着当且仅当y.compareTo(x)抛出异常时,x.compareTo(y)才抛出异常。
- 必须确保这个比较关系是可传递的:(x.compareTo(y) > 0 && y.compareTo(z) > 0)暗示着x.compareTo(z) > 0也成立。对应着equals使用规范里面的传递性。
- 必须确保x.compareTo(y) == 0暗示着所有的z都满足sgn(x.compareTo(z)) == sgn(y.compareTo(z))。
- 强烈建议(x.compareTo(y) == 0) == (x.equals(y)),但是这个并非绝对必要。一般来说,任何实现了Comparable接口的类,若违反了这个条件,都应该明确予以说明。推荐使用这样的说法:“注意,该类具有内在的排序功能,但是与equals不一致”。
示例
如果一个类有多个关键域,那么比较这些关键域的顺序非常关键。必须从最关键的域开始,逐步进行到所有的重要域。如果某个域的比较产生了非零的结果(0代表着相等),则整个比较操作结束,并返回该结果。如果最关键的域是相等的,则再比较下一个关键域,以此类推,如果所有域都是相等的,那么才返回0。例如下面的例子:
public final class PhoneNumber implements Comparable { private final short areaCode; private final short prefix; private final short lineNumber; public PhoneNumber(int areaCode, int prefix, int lineNumber) { this.areaCode = (short) areaCode; this.prefix = (short) prefix; this.lineNumber = (short) lineNumber; } @Override public int compareTo(PhoneNumber pn) { if (areaCode < pn.areaCode) return -1; if(areaCode > pn.areaCode) return 1; if (prefix < pn.prefix) return -1; if (prefix > pn.prefix) return 1; if (lineNumber < pn.lineNumber) return -1; if (lineNumber > pn.lineNumber) return 1; return 0; } }
可以改进如下:
public int compareTo(PhoneNumber pn) { int areaCodeDiff = areaCode - pn.areaCode; if (areaCodeDiff != 0) return areaCodeDiff; int prefixDiff = prefix - pn.prefix; if (0 != prefixDiff) return prefixDiff; return lineNumber - pn.lineNumber; }
使用这种方法的时候需要注意,有符号的32位整数还不足以大到能够表达任意两个32位整数的差值,如果i是一个很大的正整数,j是一个很小的负整数,i-j有可能会溢出,并且返回一个负值。
结论
在实现Comparable接口时,应该遵守这些规范,特别是在做等同性测试的时候,要和equals等同性测试结果保持一致。